package be.kdg.bombermanunlimited.client.gui;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JPanel;

import be.kdg.bombermanunlimited.client.Interfaces.IGameToGui;
import be.kdg.bombermanunlimited.client.Interfaces.IProxyGuiPlayer;
import be.kdg.bombermanunlimited.server.remote.interfaces.IGame;
import be.kdg.bombermanunlimited.server.remote.interfaces.ITile;
import be.kdg.bombermanunlimited.server.remote.interfaces.ITile.TileType;

public class Board extends JPanel implements MouseListener, IGameToGui {
	/**
	 * De klasse Board bevat alle componenten die afgebeeld worden op het scherm.
	 * Dit zijn de spelers, bommen, explosies & "foliage" (boompjes en dergelijke)
	 * 
	 * Deze klasse bevat ook functionaliteit die: 
	 * 	  Keyboard events afhandelt
	 * 	  Het teken van alle objecten (dmv de paint() method)
	 */
	private static final long serialVersionUID = -2835311795037156811L;
	//de locale player (zie GuiLocalPlayer voor uitleg over linking met server
	private GuiLocalPlayer localPlayer;
	//een lijst met de andere players, 
	private List<GuiLocalPlayer> otherPlayers;
	//de lijst met bommen, server zorgt hiervoor
	private List<GUI_Bomb> bombs;
	//explosies zijn louter voor de GUI, ze hebben niks te maken met de gamelogic
	private List<List<GUI_Explosion>> explosions;
	//random op de server gegenereerde map
	private GUI_Map map;
	private boolean gameStarted;
	private KeyboardEventGenerator keg;
	/*
	* IGame is de interface die de communicatie van player-server verzorgt
	*  	vb game.getMap(); 
	 */
	private IGame game;

	public Board(IGame game) {
		/*
		 * Bij aanroepen van constructor alles initialiseren
		 * */
		super();
		keg = new KeyboardEventGenerator(this, 15);
		setFocusable(true);
		setDoubleBuffered(true);
		otherPlayers = new ArrayList<GuiLocalPlayer>();
		bombs = new ArrayList<GUI_Bomb>();
		explosions = new ArrayList<List<GUI_Explosion>>();
		gameStarted = false;
		this.game = game;
		addMouseListener(this);
		Timer timer = new Timer(true);
		//regelt de refreshrate van de GUI, nu om de 20 ms
		timer.schedule(new RepaintTimer(), 20, 40);
	}

	/*
	 * Bij het "aflopen" van de timer:
	 * 	updateExplosions() om de explosies van frame te doen wijzigen in hun resp. spritesheets
	 * en daarna repaint() aanroepen om heel de GUI te hertekenen
	 */
	class RepaintTimer extends TimerTask {
		public void run() {
			updateExplosions();
			repaint();
		}
	}

	public void addLocalPlayer(IProxyGuiPlayer localPlayer) {
		this.localPlayer = new GuiLocalPlayer(localPlayer);
	}

	public void addOtherPlayers(List<IProxyGuiPlayer> players) {
		for (int i = 0; i < players.size(); i++) {
			otherPlayers.add(i, new GuiLocalPlayer(players.get(i)));
		}

	}

	/*
	 * wordt ook nog eens aangeroepen bij iedere verandering op server, bovenop de 20ms
	 * zodat er geen "flicker" optreedt
	 */
	public void updatePlayer() {
		repaint();
	}

	public void addBomb(int row, int column, int range) {
		bombs.add(new GUI_Bomb(row, column, range));
	}

	
	/*
	 * blowBomb() handelt de weergave van de explosies af
	 * 	mag niet over unbreakablewalls gaan
	 * 	is volledig voor gui, explosies zijn GEEN deel van 
	 *  de gamelogica
	 */
	public void blowBomb() {
		int row = bombs.get(0).getRow();
		int col = bombs.get(0).getColumn();
		int range = bombs.get(0).getRange();

		boolean left, right, up, down;
		left = right = up = down = true;
		List<GUI_Explosion> subExplosions = new ArrayList<GUI_Explosion>();

		for (int i = 0; i <= range; i++) {
			//omliggende tiles checken
			TileType typeUp = map.getTileType(row - i, col);
			TileType typeDown = map.getTileType(row + i, col);
			TileType typeLeft = map.getTileType(row, col - i);
			TileType typeRight = map.getTileType(row, col + i);
			//waarden anden om in if lussen te kijken of ze moeten getekend worden
			up = up && !(typeUp == TileType.UnbreakableWall);
			down = down && !(typeDown == TileType.UnbreakableWall);
			left = left && !(typeLeft == TileType.UnbreakableWall);
			right = right && !(typeRight == TileType.UnbreakableWall);

			//lussen die kijken of er een explosie moet geadd worden
			if (up) {
				subExplosions.add(new GUI_Explosion(bombs.get(0).getRow() - i,
						bombs.get(0).getColumn()));

				System.out.println("Exp up added");
				if (typeUp == TileType.BreakableWall) {
					up = false;
				}
			}

			if (i != 0 && down) {
				subExplosions.add(new GUI_Explosion(bombs.get(0).getRow() + i,
						bombs.get(0).getColumn()));

				System.out.println("Exp down added");
				if (typeDown == TileType.BreakableWall) {
					down = false;
				}
			}

			if (i != 0 && left) {
				subExplosions.add(new GUI_Explosion(bombs.get(0).getRow(),
						bombs.get(0).getColumn() - i));

				System.out.println("Exp left added");
				if (typeLeft == TileType.BreakableWall) {
					left = false;
				}
			}

			if (i != 0 && right) {
				subExplosions.add(new GUI_Explosion(bombs.get(0).getRow(),
						bombs.get(0).getColumn() + i));

				System.out.println("Exp right added");
				if (typeRight == TileType.BreakableWall) {
					right = false;
				}
			}
		}
		//voegt explosies toe
		explosions.add(subExplosions);
		//altijd de "voorste" bom die eerst ontploft (want vaste Timer)
		bombs.remove(0);
	}

	//afgelopen explosies uit geheugen halen, eerste explosie = eerste explosie gedaan dus 
	//niet nodig om te checken voor de rest van de ArrayList
	private void updateExplosions() {
		if (explosions.size() > 0) {
			if (explosions.get(0).size() > 0) {
				if (explosions.get(0).get(0).cleanUp()) {
					explosions.remove(0);
				}
			} else {
				explosions.remove(0);
			}
		}
	}

	//vraagt map op van server
	public void setMap(Vector<ITile> tiles) {
		this.map = new GUI_Map(tiles);

		int width = map.getMapImg().getHeight(null);
		int height = map.getMapImg().getWidth(null);
		setPreferredSize(new Dimension(width, height));
	}

	//na een explosies kan een boompje veranderen in een 
	//bewandelbare Tile, deze functie zorgt daarvoor
	public void changeTile(int index) {
		this.map.changeTile(index);
	}

	
	//adaptors voor toetsaanslagen te detecteren
	private class TAdapter extends KeyAdapter {

		public void keyReleased(KeyEvent e) {
			// System.out.println("Key released");
			localPlayer.keyReleased(e);
		}

		public void keyPressed(KeyEvent e) {
			// System.out.println("Key pressed");
			localPlayer.keyPressed(e);
		}
	}

	
	private class KeyboardEventGenerator implements Runnable, KeyListener {
		private int intervalDelay;
		private KeyEvent pressed;
		private List<KeyListener> listeners;
		private long lastFired;

		public KeyboardEventGenerator(JComponent comp, int intervalDelay) {
			this.intervalDelay = intervalDelay;
			listeners = new ArrayList<KeyListener>();
			lastFired = 0;

			comp.addKeyListener(this);
		}

		public void addListener(KeyListener l) {
			listeners.add(l);
		}

		@SuppressWarnings("unused")
		public void removeListener(KeyListener l) {
			listeners.remove(l);
		}

		private synchronized void fireKeyPressed(KeyEvent e) {
			for (KeyListener l : listeners) {
				l.keyPressed(e);
			}
		}

		private synchronized void fireKeyReleased(KeyEvent e) {
			for (KeyListener l : listeners) {
				l.keyReleased(e);
			}
		}

		@Override
		public void keyTyped(KeyEvent e) {
		}

		@Override
		public void keyPressed(KeyEvent e) {
			if (e != null) {
				if (pressed == null) {
					pressed = e;
					new Thread(this).start();
				} else if (!pressed.equals(e)) {
					pressed = e;
				}
			}
		}

		@Override
		public void keyReleased(KeyEvent e) {
			if (e != null) {
				if (pressed != null && pressed.getKeyCode() == e.getKeyCode()) {
					pressed = null;
				}

				fireKeyReleased(e);
			}
		}

		@Override
		public void run() {
			lastFired = System.currentTimeMillis();
			while (pressed != null) {
				fireKeyPressed(pressed);
				long sleepVal = intervalDelay
						- (System.currentTimeMillis() - lastFired);
				if (sleepVal > 0) {
					try {
						Thread.sleep(intervalDelay
								- (System.currentTimeMillis() - lastFired));
					} catch (InterruptedException ex) {
					}

				}
				lastFired = System.currentTimeMillis();
			}
		}
	}

	//geeft gewoon mee dat de dode spelers niet meer moeten gerendered worden
	public void setDead(int index) {
		// System.out.println("Player @ index " + index + " is dead!");
		this.otherPlayers.get(index).setDead();
	}

	//idem als setDead() maar dan voor lokale spelers
	public void gameOver() {
		this.localPlayer.setDead();
	}

	//tijdens het wachten op een vol spel: nieuwe spelers toevoegen ipv
	// via server heel de lijst opnieuw door te sturen
	public void addOtherPlayer(IProxyGuiPlayer player) {
		this.otherPlayers.add(new GuiLocalPlayer(player));
	}

	//vanaf startGame() pas luisteren naar keyboard input
	@Override
	public void startGame() {
		// System.out.println("Game started!");
		keg.addListener(new TAdapter());
		this.gameStarted = true;
	}

	//tekenen van GUI zelf
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2d = (Graphics2D) g;
		//tekenen van de map (de achtergrond dus)
		g2d.drawImage(map.getMapImg(), 0, 0, this);
		if (localPlayer != null) {
			//tekenen van de locale speler
			g2d.drawImage(localPlayer.getPlayerSprite(), localPlayer.getX(),
					localPlayer.getY(), this);
		}
		for (int i = 0; i < otherPlayers.size(); i++) {
			//tekenen van de andere spelers
			g2d.drawImage(otherPlayers.get(i).getPlayerSprite(), otherPlayers
					.get(i).getX(), otherPlayers.get(i).getY(), this);
		}
		for (int i = 0; i < explosions.size(); i++) {
			List<GUI_Explosion> explosion = explosions.get(i);

			for (int j = 0; j < explosion.size(); j++) {
				//teken explosie en doe incrementFrameCount() om 
				// naar volgende "fase" explosie te gaan
				GUI_Explosion gui_exp = explosion.get(j);
				g2d.drawImage(gui_exp.getExplosionsprite(), gui_exp.getX(),
						gui_exp.getY(), this);
				gui_exp.incrementFrameCount();
			}
		}
		for (int i = 0; i < bombs.size(); i++) {
			//teken de bommen zelf
			g2d.drawImage(bombs.get(i).getBombSprite(), bombs.get(i).getX(),
					bombs.get(i).getY(), this);
			bombs.get(i).incrementFrameCount();

		}
		Toolkit.getDefaultToolkit().sync();
		g.dispose();
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		requestFocus();
	}

	@Override
	public void mousePressed(MouseEvent e) {
	}

	@Override
	public void mouseReleased(MouseEvent e) {
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void updatePlayers() {
		repaint();
	}
}
